iT邦幫忙

2026 iThome 鐵人賽

DAY 18
0

「退款成功」這四個字,在前端、後端、金流商與客戶那裡,是四件不同的事;做到一半才發現,沒有一件被定義過。


聯調第三天,有人問了一句

昨天整合日的火勉強撲掉了。欄位語意對過一輪,線接通了,在測試環境按下「退款」,畫面會亮出綠色的「退款成功」。看板重新變綠,群組裡有人貼了慶祝訊息。

聯調第三天,年輕的後端工程師盯著金流回應的 log,隨口問了一句:

「所以金流回 200,這筆就是真的退成功了嗎?」

負責金流串接的工程師頭也沒抬:「200 只是受理。實際成功還是失敗,要等金流商的 webhook 通知,或是自己拿單號去查。」

他說完,才發現會議室安靜下來了。

前端那個秒亮的「退款成功」畫面。後端那行「收到 200 就把訂單改成已退款」的程式。兩個「完成」,全部蓋在同一個沒有人驗證過的假設上:200 等於退款成功。

專案做到一半,Ticket 開了三週,程式寫了幾千行,這是第一次有人問出這個問題:

怎樣叫退款成功?


當時團隊怎麼理解「成功」

事後回看,每個人心裡其實都有一個「成功」的定義,而且單獨看都有道理。

前端工程師想的是:API 回成功我就顯示成功、回失敗我就顯示失敗,每個功能都是這樣做的,天經地義。

後端工程師想的是:呼叫金流 API 拿到 HTTP 200 就是呼叫成功,呼叫成功就更新訂單狀態,教科書不都這樣寫。

金流串接工程師想的是:文件寫得清清楚楚,200 是受理,結果要等通知——這是常識吧,需要特別講嗎?

PM 想的是:三張 Ticket 都拖進 Done 了,完成就是完成。

四個人、四個「成功」,各自在自己那一層都沒有錯。問題從來不是誰理解錯了,而是沒有人發現「退款成功」在這個房間裡指的根本不是同一件事——而且整整三週,沒有任何一個環節逼大家把它攤開來對一次。


五層「成功」,我們只回答了半層

把這個動作解剖開來,「退款成功」至少疊了五層:

HTTP 200
≠ refund accepted(金流商受理這筆退款申請)
≠ payment reversed(錢真的被退回)
≠ state confirmed(我們的系統確認並更新狀態)
≠ user goal achieved(客戶拿回錢,而且知道拿回了)

一層一層拆。

第一層,HTTP 200。這是通訊層的回條:請求送到了、對方有回話。僅此而已,它連「對方看懂了你的請求」都不保證——很多服務照樣用 200 包著錯誤代碼回你。確認方式:讀回應本身。

第二層,refund accepted。在本案金流商的約定裡,200 搭配回應裡的結果代碼,代表「受理」:申請被收下、排入處理。受理與退到錢的距離,大概等於投出履歷與拿到聘書的距離。而且從這一層開始,主導權就不在我們系統手上了。

第三層,payment reversed。錢真的動了:款項被退回持卡人那一側。這件事發生在別人的系統、別人的時間表裡,誰來告訴你?兩條路——金流商的 webhook 主動通知,或我們拿單號主動查詢。而 webhook,昨天才盤點過,三方都以為不歸自己,沒有人串。也就是說,這一層在我們系統裡目前是全盲的。金流串接工程師順帶補了一句:webhook 偶爾會重送,同一筆通知可能來兩次。沒有人接話,會議記錄上也沒有這一行。

第四層,state confirmed。金流商說退了,我們的訂單狀態要跟著變,而且要跟金流商的紀錄對得起來——財務對帳,對的就是這兩邊。金流商日結,對帳檔隔天才有,帳面上的最終確認天生慢一天。而我們的後端在收到 200 的那一秒,就把訂單改成「已退款」:拿第一層的回條,蓋了第四層的章。

第五層,user goal achieved。客戶按下退款,他要的不是一個綠色勾勾,是錢回到帳上、而且他知道回來了。信用卡退款從受理到出現在帳單上,往往要好幾個工作天。前端在第零秒顯示「退款成功」,等於替金流商簽了一張它從來沒答應過的支票。

每一層的「成功」,都要回答同樣的兩個問題:這一層成功是什麼意思?由誰、用什麼機制確認?

這個專案在動工前回答了幾層?零層。動工三週後呢?半層——我們現在知道 200 是受理了,但受理之後的每一層,仍然沒有人負責確認。


「處理中」是誠實,不是失敗

那麼,從受理到確認之間,畫面該顯示什麼?

三個字:處理中。

很多工程師不喜歡這三個字,覺得像是系統做不到即時、像是自己的失敗。剛好相反——錢正在別人的系統裡移動、結果還沒回來,「處理中」是這個當下唯一誠實的答案。還不知道結果就說還不知道,這不叫體驗差,這叫不說謊。

問題是前端的畫面只有兩格:成功、失敗。昨天說過,前端等的是一個 boolean。二態的畫面裝不下三態的世界,多出來的那一態,就被四捨五入成「成功」。

這也不是什麼新問題。瀑布的做法:設計階段的介面規格就要寫明「此 API 為非同步、結果用什麼機制確認、確認前狀態為何」,驗收依據跟著走。敏捷的做法:這條 Story 的 Acceptance Criteria 本來就該寫出可觀察的完成條件——按下退款之後,使用者在哪個時點看到什麼(Day 10 說過,AC 不是文件格式,是把「怎樣算好」變成共識)。兩邊都沒有一條規則叫做「回 200 就算成功」。

缺的從來不是方法論。缺的是動工之前,有人把今天標題那個問題問出來。


那個會問問題的人不在

把時間倒回第二部的世界,這件事大概根本不會成為一件事。

如果資深工程師在場,接金流的第一天他就會問:「退款是同步還是非同步?」——被非同步咬過的人,看到「金流」兩個字就會先問這一題。然後他會順手在白板上畫出狀態:申請、處理中、成功、失敗;會提醒 webhook 要列進工作項;會記得對帳檔隔天才有。專案會順順地往前走,順到沒有人發現這裡曾經有一個坑。

這次他被借調去救另一個專案,不在。

於是暴露出一個很有意思的事實:知識其實一直都在場。金流商的文件白紙黑字寫著 200 是受理;金流串接工程師讀過,也一直知道。他沒有講,因為沒有人問,而他以為這是常識——就像每個人都以為 webhook 有人串。

所以大神真正的功能,從來不只是知道答案,而是會在對的時間把對的問題問出來。Day 15 的開工前五問裡,有一格就叫「怎樣算成功」。那一格不需要天分才填得出來,它需要的只是流程裡有一個位置,逼你在動工之前填掉它。

三週前沒有人填。今天,整個團隊用一場鴉雀無聲的聯調會議,把它補填完畢。


這次到底誰在吸收代價?

盤點帳單。那個秒亮的「退款成功」畫面,已經在測試環境跑了兩週、展示過、被稱讚過;現在後端的狀態要重想、前端的畫面要重排、沒人認領的 webhook 要補——這些工,原本不在任何人的估算裡。

Scope       □   一項都沒有少
Time        □   上線日還是沒有人敢動
Cost        □   沒有加人
Quality     ■   蓋在錯誤完成語意上的畫面與狀態,跑了兩週
Risk        □   沒有人重新評估還有幾層沒定義
人          ■   聯調加班、重寫狀態、補做沒人認領的工   ← 大神不在,還是這格

Day 15 說過,這個案子的風險是一顆一顆堆上去的未爆彈。今天爆的這顆叫完成語意,拆彈方式一如既往:品質先賠,人再加班。

完成語意晚一天定義,就有人拿著錯的定義多施工一天;今天多蓋上去的每一層,都是明天要拆的。


今日 Artifact|完成語意定義表

任何跨系統的動作——退款、付款、寄通知、開發票、同步資料——動工前先把這張表填完:

□ 這個動作從發起到結束,一共有幾層「完成」?
  (例:送出 → 受理 → 實際生效 → 狀態確認 → 使用者目標)
□ 每一層的「成功」是什麼意思?
□ 每一層由誰、或用什麼機制確認?
  (回應代碼/非同步通知/主動查詢/對帳檔:__)
□ 最後一層確認之前與之後,使用者各看到什麼?

最後一格填不出來,你的畫面上遲早會出現一個「猜的成功」;填得出來,你才有資格決定那個綠色勾勾該在哪一秒亮起。


今日一句

系統回你 200,意思是「我聽到了」;把「我聽到了」翻譯成「我做完了」,是這個專案最貴的一次翻譯。

那天下午,前端把畫面改成三個狀態:處理中、成功、失敗。大家看著新畫面,覺得這個功能終於誠實了。有人提議:拿給客服主管看看吧,順便展示一下進度。

她盯著「處理中」三個字看了幾秒,說出的第一句話是:

「不是這樣。超過七天的,要先給財務簽核。」

會議室再度安靜。等等——這句話,算需求變更嗎?明天來把這件事分清楚。


上一篇
Day 17|大家都完成了,為什麼整套系統不能用?
下一篇
Day 19|客戶說「不是這樣」,這叫需求變更嗎?
系列文
你以為自己很敏捷,其實連瀑布都沒做好——30 天從錯誤承諾、大神救火,到沒有大神也跑得動的開發方法20
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言